//------------------------------------------------------------------------------
// <copyright file="TestHooks.cpp" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

#include "pch.h"
#include "TestHooks.h"
#include "TestHooksErrorCodes.h"

#include <ppltasks.h>
#include <winerror.h>
#include <robuffer.h>

using namespace Microsoft::WRL;
using namespace Windows::Storage::Streams;
using namespace Windows::System::Threading;
using namespace concurrency;

const int TEST_EXCEPTION_1 = RpcError::MakeHookError(600);
const int TEST_EXCEPTION_2 = RpcError::MakeHookError(601);
const int TEST_EXCEPTION_3 = RpcError::MakeHookError(602);

namespace
{
	RpcServer^ rpcServer;
	RpcServer^ rpcServer2;
	RpcServer^ rpcServer3;
	RpcServer^ rpcServerWithQueueSize;

	__int16 testHookOrderCounter = 0;
}

// Creates a task that completes after the specified delay.
void sleep(unsigned int milliseconds)
{
	auto start = GetTickCount64();
    for (unsigned long long i = 0; i < 0xFFFFFFFFFFFFFFFF; i++)
	{
		if ((GetTickCount64() - start) > milliseconds)
		{
            break;
        }
    }
}

HRESULT ReverseIBuffer(IBuffer^ buffer)
{
	// Query the IBufferByteAccess interface.
	Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
    HRESULT hr = reinterpret_cast<IInspectable*>(buffer)->QueryInterface(IID_PPV_ARGS(&bufferByteAccess));

	if (FAILED(hr))
	{
		return hr;
	}

    // Retrieve the buffer data.
    unsigned __int8* bytes = nullptr;
    hr = bufferByteAccess->Buffer(&bytes);

	if (FAILED(hr))
	{
		return hr;
	}

	// Reverse the byte array
	std::reverse(bytes, bytes + buffer->Length);

	return S_OK;
}

namespace CppDemo
{
	TestHooks::TestHooks(void)
	{
	}

	void TestHooks::Shutdown()
	{
		m_ProcessTimer->Cancel();
		try
		{
			rpcServer->Stop();
		}
		catch(...)
		{
		}
		try
		{
			rpcServer2->Stop();
		}
		catch(...)
		{
		}
		try
		{
			rpcServer3->Stop();
		}
		catch(...)
		{
		}
	}

	// Initialize the Direct3D resources required to run.
	void TestHooks::Initialize(int port)
	{
		rpcServer = ref new RpcServer(RpcServer::DefaultServerId, port == 0 ? RpcServer::DefaultPort : port);

		rpcServer->RegisterHookMethod(L"OutputNoInput", ref new RpcHook(OutputNoInput));
		rpcServer->RegisterHookMethod(L"InputNoOutput", ref new RpcHook(InputNoOutput));
		rpcServer->RegisterHookMethod(L"InputOutput", ref new RpcHook(InputOutput));
		rpcServer->RegisterHookMethod(L"NoInputNoOutput", ref new RpcHook(NoInputNoOutput));
		rpcServer->RegisterHookMethod(L"MultiInputMultiOutput", ref new RpcHook(MultiInputMultiOutput));
		rpcServer->RegisterHookMethod(L"HookTimesOut", ref new RpcHook(HookTimesOut));
		rpcServer->RegisterHookMethod(L"HookWaitTenSeconds", ref new RpcHook(HookWaitTenSeconds));
		rpcServer->RegisterHookMethod(L"ReturnLargeNumberOfValues", ref new RpcHook(ReturnLargeNumberOfValues));
		rpcServer->RegisterHookMethod(L"ReturnErrorCode", ref new RpcHook(ReturnErrorCode));
		rpcServer->RegisterHookMethod(L"SimultaneousHookCalls", ref new RpcHook(SimultaneousHookCalls));
		rpcServer->RegisterHookMethod(L"LargeNumberOfInputValues", ref new RpcHook(LargeNumberOfInputValues));
		rpcServer->RegisterHookMethod(L"EchoStrings", ref new RpcHook(EchoStrings));
		rpcServer->RegisterHookMethod(L"RetrieveMissingRpcArchiveField", ref new RpcHook(RetrieveMissingRpcArchiveField));
		rpcServer->RegisterHookMethod(L"RpcArchiveValueIsNull", ref new RpcHook(RpcArchiveValueIsNull));
		rpcServer->RegisterHookMethod(L"CallProcessPendingRequestsOnceOnServer3", ref new RpcHook(CallProcessPendingRequestsOnceOnServer3));
		rpcServer->RegisterHookMethod(L"InputOutputIntCollection", ref new RpcHook(InputOutputIntCollection));
		rpcServer->RegisterHookMethod(L"InputOutputInt64Collection", ref new RpcHook(InputOutputInt64Collection));
		rpcServer->RegisterHookMethod(L"InputOutputFloatCollection", ref new RpcHook(InputOutputFloatCollection));
		rpcServer->RegisterHookMethod(L"InputOutputStringCollection", ref new RpcHook(InputOutputStringCollection));
		rpcServer->RegisterHookMethod(L"InputOutputIntCollectionAndIntNumber", ref new RpcHook(InputOutputIntCollectionAndIntNumber));
		rpcServer->RegisterHookMethod(L"InputOutputMultipleCollections", ref new RpcHook(InputOutputMultipleCollections));
		rpcServer->RegisterHookMethod(L"TriggerEvent", ref new RpcHook(TriggerEvent));
		rpcServer->RegisterHookMethod(L"AddArchiveToArchive", ref new RpcHook(AddArchiveToArchive));
		rpcServer->RegisterHookMethod(L"AddArchiveToArchiveWithIntColl", ref new RpcHook(AddArchiveToArchiveWithIntColl));
		rpcServer->RegisterHookMethod(L"AddArchiveToArchiveWithStringColl", ref new RpcHook(AddArchiveToArchiveWithStringColl));
		rpcServer->RegisterHookMethod(L"AddArchiveToArchiveWithInt64Coll", ref new RpcHook(AddArchiveToArchiveWithInt64Coll));
		rpcServer->RegisterHookMethod(L"InputOutputArchiveCollectionToArchive", ref new RpcHook(InputOutputArchiveCollectionToArchive));
		rpcServer->RegisterHookMethod(L"InputOutputArchiveCollectionToArchiveCollections", ref new RpcHook(InputOutputArchiveCollectionToArchiveCollections));
		rpcServer->RegisterHookMethod(L"InputRpcArchiveToAnotherRpcArchiveForLoad", ref new RpcHook(InputRpcArchiveToAnotherRpcArchiveForLoad));
		rpcServer->RegisterHookMethod(L"InputRpcArchiveToAnotherRpcArchiveCollectionForLoad", ref new RpcHook(InputRpcArchiveToAnotherRpcArchiveCollectionForLoad));
		rpcServer->RegisterHookMethod(L"InputPlayerNoOutput", ref new RpcHook(InputPlayerNoOutput));
		rpcServer->RegisterHookMethod(L"NoInputPlayerOutput", ref new RpcHook(NoInputPlayerOutput));
		rpcServer->RegisterHookMethod(L"InputPlayerOutputPlayer", ref new RpcHook(InputPlayerOutputPlayer));
		rpcServer->RegisterHookMethod(L"ThrowException", ref new RpcHook(ThrowException));
		rpcServer->RegisterHookMethod(L"AddBinaryDataToArchive", ref new RpcHook(AddBinaryDataToArchive));
		rpcServer->RegisterHookMethod(L"AddBinaryDataCollectionToArchive", ref new RpcHook(AddBinaryDataCollectionToArchive));
		rpcServer->RegisterHookMethod(L"NoInputBinaryDataOutput", ref new RpcHook(NoInputBinaryDataOutput));
		rpcServer->RegisterHookMethod(L"EchoBinary", ref new RpcHook(EchoBinary));
		rpcServer->RegisterHookMethod(L"StartServerWithQueueSize", ref new RpcHook(StartServerWithQueueSize));
		rpcServer->RegisterHookMethod(L"StopServerWithQueueSize", ref new RpcHook(StopServerWithQueueSize));
		rpcServer->RegisterHookMethod(L"RunServerWithQueueSize", ref new RpcHook(RunServerWithQueueSize));

		try
		{
			rpcServer->Start();
		}
		catch(...)
		{
			OutputDebugString(L"Failed to start the first server.\n");
		}

		// Warning: If you modify this guid then you need to modify the same guid value in Rpc.Tests.XboxEndToEndTests.cs file.
		// {4FC5C96E-2ECD-4A45-BF88-59EFFC184682}
		static const GUID guid = { 0x4fc5c96e, 0x2ecd, 0x4a45, { 0xbf, 0x88, 0x59, 0xef, 0xfc, 0x18, 0x46, 0x82} };

		rpcServer2 = ref new RpcServer(guid);
		rpcServer2->RegisterHookMethod(L"InputOutputSubtraction", ref new RpcHook(InputOutputSubtraction));
		rpcServer2->RegisterHookMethod(L"StopTheServer", ref new RpcHook(StopTheServer));
		rpcServer2->RegisterHookMethod(L"CallProcessPendingRequestsOnceOnServer3", ref new RpcHook(CallProcessPendingRequestsOnceOnServer3));
		try
		{
			rpcServer2->Start();
		}
		catch(...)
		{
			OutputDebugString(L"Failed to start the second server.\n");
		}

		// {48810C25-BC83-4934-9085-8B00275FD4F5}
		static const GUID guid3 = { 0x48810c25, 0xbc83, 0x4934, { 0x90, 0x85, 0x8b, 0x0, 0x27, 0x5f, 0xd4, 0xf5 } };
		rpcServer3 = ref new RpcServer(guid3);
		rpcServer3->RegisterHookMethod(L"InputOutput", ref new RpcHook(InputOutput));
		rpcServer3->RegisterHookMethod(L"InputOutputImmediate", ref new RpcHook(InputOutput), true);
		rpcServer3->RegisterHookMethod(L"InputNoOutput", ref new RpcHook(InputNoOutput));
		rpcServer3->RegisterHookMethod(L"OutputNoInput", ref new RpcHook(OutputNoInput));
		rpcServer3->RegisterHookMethod(L"MultiInputMultiOutput", ref new RpcHook(MultiInputMultiOutput));
		rpcServer3->RegisterHookMethod(L"HookOrderIncrementNoOutput", ref new RpcHook(HookOrderIncrementNoOutput));
		rpcServer3->RegisterHookMethod(L"GetHookOrderCounter", ref new RpcHook(GetHookOrderCounter));
		rpcServer3->RegisterHookMethod(L"TriggerEvent", ref new RpcHook(TriggerEvent));
		try
		{
			rpcServer3->Start();
		}
		catch(...)
		{
			OutputDebugString(L"Failed to start the third server.\n");
		}

		StartProcessPendingRequests();
	}

	void TestHooks::StartProcessPendingRequests()
	{
		auto time = Windows::Foundation::TimeSpan();
		time.Duration = 5000000; // 1s
		m_ProcessTimer = ThreadPoolTimer::CreatePeriodicTimer( ref new TimerElapsedHandler([this](ThreadPoolTimer^ timer)
		{
			ProcessPendingRequests();
		}),time);
	}

	void TestHooks::ProcessPendingRequests()
	{
		try
		{
			rpcServer->ProcessPendingRequests();
		}
		catch(...)
		{
			OutputDebugStringW(L"Hi");
		}
		try
		{
			rpcServer2->ProcessPendingRequests();
		}
		catch(...)
		{
			OutputDebugStringW(L"Hi");
		}
	}

	// Hook method to call raise event for test runs
	RpcHookCode TestHooks::TriggerEvent(RpcArchive^ eventName, RpcArchive^ eventParameter)
	{
		try
		{
            auto stringValue = eventName->GetNamedStringValue(L"eventName");
                                         
            eventParameter->SetNamedStringValue(L"result", L"Test Event Parameter");

            // This need to be implemented
            rpcServer->RaiseEvent(stringValue, eventParameter);

            // raise event for server for test case 12
            // If required we can optionally send server name as parameter in RpcArchive object to avoid both raise events to be called
            rpcServer3->RaiseEvent(stringValue, eventParameter);
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}	

	RpcHookCode TestHooks::OutputNoInput(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		
		try
		{
			output->SetNamedIntValue(L"result", 3871568);
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::EchoStrings(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto numStrings = input->GetNamedWordValue(L"numStrings");
			auto stringArray = ref new Platform::Array<Platform::String^>(numStrings);

			
			wchar_t fieldName[10];
            for (int i = 0; i < numStrings; ++i)
			{
				std::swprintf(fieldName, sizeof(fieldName), L"input%03d", i);

				auto namedValue = input->GetNamedStringValue(ref new Platform::String(fieldName));
				stringArray[i] = namedValue;
            }

            output->SetNamedWordValue(L"numStrings", numStrings);

            for (int i = 0; i < numStrings; ++i)
			{
				std::swprintf(fieldName, sizeof(fieldName), L"output%03d", i);

                auto outgoingString = L"Echo:" + stringArray[i];

				output->SetNamedStringValue(ref new Platform::String(fieldName), outgoingString);
            }

		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputNoOutput(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam1 = input->GetNamedFloatValue(L"param01");

            if (abs(inputParam1 - 3.14156) > 0.0001)
			{
                return E_INVALIDARG;
            }
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutput(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam1 = input->GetNamedIntValue(L"param01");
            auto inputParam2 = input->GetNamedIntValue(L"param02");

            if (inputParam1 != 211 || inputParam2 != 87)
			{
                return E_INVALIDARG;
            }

            output->SetNamedIntValue(L"result", inputParam1 + inputParam2);

		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutputIntCollection(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam01 = input->GetNamedIntCollection(L"param01");
			std::reverse(inputParam01->begin(), inputParam01->end());
            output->SetNamedIntCollection(L"result", inputParam01);

		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutputInt64Collection(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam01 = input->GetNamedInt64Collection(L"param01");
            std::reverse(inputParam01->begin(), inputParam01->end());
            output->SetNamedInt64Collection(L"result", inputParam01);
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutputFloatCollection(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam01 = input->GetNamedFloatCollection(L"param01");
            std::reverse(inputParam01->begin(), inputParam01->end());
            output->SetNamedFloatCollection(L"result", inputParam01);
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutputStringCollection(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam01 = input->GetNamedStringCollection(L"stringCollection");
            std::reverse(inputParam01->begin(), inputParam01->end());
            output->SetNamedStringCollection(L"result", inputParam01);
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutputIntCollectionAndIntNumber(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam02 = input->GetNamedIntValue(L"param02");
            auto inputParam01 = input->GetNamedIntCollection(L"param01");
            for (unsigned int i = 0; i < inputParam01->Length; ++i)
			{
                inputParam01[i] += inputParam02;
            }
            output->SetNamedIntCollection(L"result", inputParam01);

		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutputMultipleCollections(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			// String Collection
            auto stringBuffer1 = input->GetNamedStringCollection(L"param01");
			std::reverse(stringBuffer1->begin(), stringBuffer1->end());
            output->SetNamedStringCollection(L"result1", stringBuffer1);

            // int Collection
            auto inputParam02 = input->GetNamedIntCollection(L"param02");
            std::reverse(inputParam02->begin(), inputParam02->end());
            output->SetNamedIntCollection(L"result2", inputParam02);

            // String Collection
            auto stringBuffer3 = input->GetNamedStringCollection(L"param03");
            std::reverse(stringBuffer3->begin(), stringBuffer3->end());
            output->SetNamedStringCollection(L"result3", stringBuffer3);

            // Float Collection
            auto inputParam04 = input->GetNamedFloatCollection(L"param04");
            std::reverse(inputParam04->begin(), inputParam04->end());
            output->SetNamedFloatCollection(L"result4", inputParam04);

		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutputSubtraction(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam1 = input->GetNamedShortValue(L"param01");
            auto inputParam2 = input->GetNamedShortValue(L"param02");

            if (inputParam1 != 50 || inputParam2 != 30)
			{
                return E_INVALIDARG;
            }

            output->SetNamedShortValue(L"result", inputParam1 - inputParam2);

		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::NoInputNoOutput(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		UNREFERENCED_PARAMETER(output);

		return S_OK;
	}

	RpcHookCode TestHooks::StopTheServer(RpcArchive^ input, RpcArchive^ output)
	{
		auto result = 0;
        try
		{
            rpcServer2->Stop();
        }
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }
        
		output->SetNamedInt64Value(L"result", result);

		return S_OK;
	}

	RpcHookCode TestHooks::CallProcessPendingRequestsOnceOnServer3(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			UNREFERENCED_PARAMETER(input);
			UNREFERENCED_PARAMETER(output);

			rpcServer3->ProcessPendingRequests();
			sleep(500);
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::MultiInputMultiOutput(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam1 = input->GetNamedByteValue(L"param01");
            auto inputParam2 = input->GetNamedByteValue(L"param02");
            auto inputParam3 = input->GetNamedDoubleValue(L"param03");

            if (inputParam1 != 112 || inputParam2 != 79 || inputParam3 != 93.125)
			{
                return E_INVALIDARG;
            }

            output->SetNamedByteValue(L"addOneAndTwo", inputParam1 + inputParam2);
            output->SetNamedDoubleValue(L"paramThreeSquared", inputParam3 * inputParam3);
            output->SetNamedByteValue(L"subtractTwoFromOne", inputParam1 - inputParam2);

		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::HookTimesOut(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		UNREFERENCED_PARAMETER(output);

		sleep(31000);

		return S_OK;
	}

	RpcHookCode TestHooks::HookWaitTenSeconds(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		UNREFERENCED_PARAMETER(output);

		sleep(10000);

		return S_OK;
	}

	RpcHookCode TestHooks::ReturnLargeNumberOfValues(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto numOutputValues = input->GetNamedIntValue(L"numOutputValues");
            for (int i = 0; i < numOutputValues; ++i)
			{
				wchar_t fieldName[7];
                std::swprintf(fieldName, sizeof(fieldName), L"out%03d", i);
                output->SetNamedIntValue(ref new Platform::String(fieldName), i);
            }
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::ReturnErrorCode(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		UNREFERENCED_PARAMETER(output);

		return E_FAIL;
	}

	RpcHookCode TestHooks::SimultaneousHookCalls(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto param = input->GetNamedWordValue(L"param");

            if (param != 52 && param != 71)
			{
                return E_INVALIDARG;
            }

            output->SetNamedWordValue(L"result", param * 100);
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::LargeNumberOfInputValues(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto numParams = input->GetNamedIntValue(L"numParams");

            auto sum = 0;
            for (int i = 0; i < numParams; ++i)
			{
				wchar_t fieldName[7];
                std::swprintf(fieldName, sizeof(fieldName), L"in%03d", i);
                auto currentValue = input->GetNamedIntValue(ref new Platform::String(fieldName));

                sum += currentValue;
            }

            output->SetNamedIntValue(L"result", sum);
		} 
		catch (Platform::COMException^ e)
		{
            return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::RetrieveMissingRpcArchiveField(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			UNREFERENCED_PARAMETER(output);

			auto notUsed = input->GetNamedDoubleValue(L"nonExistentField");
		}
		catch(Platform::COMException^ e)
		{
			return ARCHIVE_FIELD_NOT_FOUND;
		}

		return S_OK;
	}

	RpcHookCode TestHooks::RpcArchiveValueIsNull(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		UNREFERENCED_PARAMETER(output);

		return ARCHIVE_VALUE_IS_NULL;
	}

	RpcHookCode TestHooks::HookOrderIncrementNoOutput(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		UNREFERENCED_PARAMETER(output);

		++testHookOrderCounter;

		return S_OK;
	}

	RpcHookCode TestHooks::GetHookOrderCounter(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
            output->SetNamedShortValue(L"result", testHookOrderCounter);
        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::AddArchiveToArchive(RpcArchive^ input, RpcArchive^ output)
	{
		RpcArchive^ outRpcArchive = ref new RpcArchive();
		RpcArchive^ inRpcArchive;

        try
		{
            inRpcArchive = input->GetNamedArchiveValue(L"param01");
        
			auto result = InputOutput(inRpcArchive, outRpcArchive);

			if (result < 0)
			{
				return result;
			}

            output->SetNamedArchiveValue(L"resultLevel1", outRpcArchive);
        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::AddArchiveToArchiveWithIntColl(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			RpcArchive^ outRpcArchive = ref new RpcArchive();
			RpcArchive^ inRpcArchive;

			inRpcArchive = input->GetNamedArchiveValue(L"param01");

			// Calling existing Hook method
			InputOutputIntCollection(inRpcArchive, outRpcArchive);

			// Setting the output of Hook method
			output->SetNamedArchiveValue(L"resultLevel1", outRpcArchive);

        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::AddArchiveToArchiveWithStringColl(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inRpcArchive1 = input->GetNamedArchiveValue(L"param01");
            auto inRpcArchive2 = inRpcArchive1->GetNamedArchiveValue(L"param01");

            auto outRpcArchive1 = ref new RpcArchive();
            auto outRpcArchive2 = ref new RpcArchive();

            InputOutputStringCollection(inRpcArchive2, outRpcArchive1);

            outRpcArchive2->SetNamedArchiveValue(L"resultLevel2", outRpcArchive1);

            output->SetNamedArchiveValue(L"resultLevel1", outRpcArchive2);
		
        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::AddArchiveToArchiveWithInt64Coll(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inRpcArchive = input->GetNamedArchiveValue(L"param01");

            auto outRpcArchive = ref new RpcArchive();

            InputOutputInt64Collection(inRpcArchive, outRpcArchive);

            output->SetNamedArchiveValue(L"resultLevel1", outRpcArchive);

        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutputArchiveCollectionToArchive(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto inputParam01 = input->GetNamedArchiveCollection(L"param01");
            auto outputParam01 = ref new Platform::Array<RpcArchive^>(inputParam01->Length);

			for (auto i = outputParam01->begin(); i != outputParam01->end(); ++i)
			{
                *i = ref new RpcArchive();
            }

            auto inputParamInt = inputParam01[0]->GetNamedIntValue(L"param01");
            outputParam01[0]->SetNamedIntValue(L"result", inputParamInt + 1);

            auto inputParamLong = inputParam01[1]->GetNamedInt64Value(L"param01");
            outputParam01[1]->SetNamedInt64Value(L"result", inputParamLong - 1);

            auto inputParamFloat = inputParam01[2]->GetNamedFloatValue(L"param01");
            outputParam01[2]->SetNamedFloatValue(L"result", inputParamFloat * 3);

            EchoStrings(inputParam01[3], outputParam01[3]);
            output->SetNamedArchiveCollection(L"resultLevel1", outputParam01);
        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputOutputArchiveCollectionToArchiveCollections(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			const int IN_OUT_ARCHIVES_COUNT = 4;

			auto inputParam01 = input->GetNamedArchiveCollection(L"param01");
			auto outputParam01 = ref new Platform::Array<RpcArchive^>(IN_OUT_ARCHIVES_COUNT);

            for (auto i = outputParam01->begin(); i != outputParam01->end(); ++i)
			{
                *i = ref new RpcArchive();
            }

            InputOutputIntCollection(inputParam01[0], outputParam01[0]);
            InputOutputInt64Collection(inputParam01[1], outputParam01[1]);
            InputOutputFloatCollection(inputParam01[2], outputParam01[2]);
            InputOutputStringCollection(inputParam01[3], outputParam01[3]);

            output->SetNamedArchiveCollection(L"resultLevel1", outputParam01);

        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputRpcArchiveToAnotherRpcArchiveForLoad(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto rpcArchiveInput = input->GetNamedArchiveValue(L"rpcMultilevel");
			auto rpcArchiveOutput = ref new RpcArchive();
			auto level = input->GetNamedIntValue(L"level");

			if (level > 0)
			{
				for (int i = 1; i < level; ++i)
				{
					auto rpcArchiveInputNode = rpcArchiveInput->GetNamedArchiveValue(L"rpcNode");
					rpcArchiveInput = rpcArchiveInputNode;
				}

				InputOutputMultipleCollectionsForRpcArchive(rpcArchiveInput, rpcArchiveOutput);

				for (int i = 1; i < level; ++i)
				{
					auto rpcArchiveOutputTemp = ref new RpcArchive();
					rpcArchiveOutputTemp->SetNamedArchiveValue(L"rpcNodeResult", rpcArchiveOutput);
					rpcArchiveOutput = rpcArchiveOutputTemp;
				}

				output->SetNamedArchiveValue(L"rpcMultilevelResult", rpcArchiveOutput);
			}
			else
			{
				return E_FAIL;
			}
        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputRpcArchiveToAnotherRpcArchiveCollectionForLoad(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto rpcArchiveInput = input->GetNamedArchiveValue(L"rpcMultilevel");
            auto rpcArchiveOutput = ref new RpcArchive();

            auto level = input->GetNamedIntValue(L"level");

            if (level > 0)
			{
                for (int i = 1; i < level; ++i)
				{
                    auto rpcArchiveInputNode = rpcArchiveInput->GetNamedArchiveValue(L"rpcNode");
                    rpcArchiveInput = rpcArchiveInputNode;
                }

                auto inputParam01 = rpcArchiveInput->GetNamedArchiveCollection(L"rpcCollection");
				auto outputParam01 = ref new Platform::Array<RpcArchive^>(inputParam01);
                for (auto i = outputParam01->begin(); i != outputParam01->end(); ++i)
				{
					*i = ref new RpcArchive();
				}

                for (unsigned int i = 0; i < inputParam01->Length; ++i)
				{
                    InputOutputMultipleCollectionsForRpcArchive(inputParam01[i], outputParam01[i]);
                }

                rpcArchiveOutput->SetNamedArchiveCollection(L"rpcCollectionResult", outputParam01);

                for (int i = 1; i < level; ++i)
				{
                    auto rpcArchiveOutputTemp = ref new RpcArchive();
                    rpcArchiveOutputTemp->SetNamedArchiveValue(L"rpcNodeResult", rpcArchiveOutput);
                    rpcArchiveOutput = rpcArchiveOutputTemp;
                }

                output->SetNamedArchiveValue(L"rpcMultilevelResult", rpcArchiveOutput);

            }
			else
			{
                return E_FAIL;
            }
        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK; 
	}

	RpcHookCode TestHooks::InputOutputMultipleCollectionsForRpcArchive(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			// Part 1 - which is a string collection
			auto stringBuffer1 = input->GetNamedStringCollection(L"param01");
			std::reverse(stringBuffer1->begin(), stringBuffer1->end());
			output->SetNamedStringCollection(L"result1", stringBuffer1);

			// Part 2 - which is a float collection
			auto inputParam02 = input->GetNamedFloatCollection(L"param02");
			std::reverse(inputParam02->begin(), inputParam02->end());
			output->SetNamedFloatCollection(L"result2", inputParam02);

			// Part 3 - which is a int collection
			auto inputParam03 = input->GetNamedIntCollection(L"param03");
			std::reverse(inputParam03->begin(), inputParam03->end());
			output->SetNamedIntCollection(L"result3", inputParam03);
        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::ThrowException(RpcArchive^ input, RpcArchive^ output)
	{
		try
		{
			auto errorCode = input->GetNamedIntValue(L"errorCode");

			switch (errorCode) {
				case 600:
					return TEST_EXCEPTION_1;
				case 601:
					return TEST_EXCEPTION_2;
				case 602:
					return TEST_EXCEPTION_3;
				default:
					return 0;
			}
        }
		catch (Platform::COMException^ e)
		{
			return e->HResult;
        }

		return S_OK;
	}

	RpcHookCode TestHooks::InputPlayerNoOutput(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(output);

		try
		{
			RpcArchive^ playerArchive = input->GetNamedArchiveValue(L"playerData");

			Player^ p = ref new Player(playerArchive);

			return VerifyIncomingPlayerData(p);
	
		}
		catch(Platform::COMException^ ex)
		{
			return ex->HResult;
		}

		return S_OK;
	}

	RpcHookCode TestHooks::AddBinaryDataToArchive(RpcArchive^ input, RpcArchive^ output)
	{	
		// Get the input blob
		RpcBinaryData^ blob = input->GetNamedBlobValue(L"param01");		
		
		// Get the raw byte array from the blob buffer
		HRESULT hr = ReverseIBuffer(blob->Data);		

		if (FAILED(hr))
		{
			return hr;
		}

		// Output modified buffer
		output->SetNamedBlobValue(L"param01", ref new RpcBinaryData(blob->Data));

		return S_OK;
	}
	
	RpcHookCode TestHooks::AddBinaryDataCollectionToArchive(RpcArchive^ input, RpcArchive^ output)
	{
		HRESULT hr = E_FAIL;

		// Get input blobs
		Platform::Array<RpcBinaryData^>^ inputBlobs = input->GetNamedBlobCollection(L"param01");

		// Reverse bytes in each one
		if (inputBlobs->Length > 0)
		{
			for (RpcBinaryData^* blob = inputBlobs->begin(); blob != inputBlobs->end(); ++blob)
			{
				hr = ReverseIBuffer((*blob)->Data);

				if (FAILED(hr))
				{
					return hr;
				}
			}
		}

		// Output modified blob collection
		output->SetNamedBlobCollection(L"result", inputBlobs);

		return hr;
	}

	RpcHookCode TestHooks::NoInputBinaryDataOutput(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);

		// Create and initialize an arbitrary array of bytes
		const int ArraySize = 5;

		Platform::Array<uint8>^ blobData = ref new Platform::Array<uint8>(ArraySize);

		for(uint8 i = 0; i < blobData->Length; ++i)
		{
			blobData[i] = i;
		}

		// Wrap the array into IBuffer to prepare for storing in RPC blob
		IBuffer^ blobBuffer = Windows::Security::Cryptography::CryptographicBuffer::CreateFromByteArray(blobData);
		
		// Output the blob containing the byte array
		output->SetNamedBlobValue(L"result", ref new RpcBinaryData(blobBuffer));

		return S_OK;
	}

	RpcHookCode TestHooks::EchoBinary(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(output);

		try
		{
			output->SetNamedBlobValue(L"blob", input->GetNamedBlobValue(L"blob"));	
		}
		catch(Platform::COMException^ ex)
		{
			return ex->HResult;
		}

		return S_OK;
	}

	RpcHookCode TestHooks::StartServerWithQueueSize(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(output);

		// {456F8F5C-0B90-465B-8D4E-D10CC8D690C0}
		static const GUID guid4 = { 0x456f8f5c, 0xb90, 0x465b, { 0x8d, 0x4e, 0xd1, 0xc, 0xc8, 0xd6, 0x90, 0xc0 } };

		HRESULT hr = E_FAIL;

		try
		{
			unsigned int queueSize = input->GetNamedDwordValue(L"queueSize");	

			rpcServerWithQueueSize = ref new RpcServer(guid4, queueSize);

			rpcServerWithQueueSize->Start();
		}
		catch (Platform::COMException^ ex)
		{
			return ex->HResult;
		}

		return S_OK;
	}

	RpcHookCode TestHooks::StopServerWithQueueSize(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		UNREFERENCED_PARAMETER(output);

		try
		{
			if (rpcServerWithQueueSize != nullptr)
			{
				rpcServerWithQueueSize->Stop();
				rpcServerWithQueueSize = nullptr;
			}
		}
		catch (Platform::COMException^ ex)
		{
			return ex->HResult;
		}

		return S_OK;
	}

	RpcHookCode TestHooks::RunServerWithQueueSize(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		UNREFERENCED_PARAMETER(output);

		try
		{
			if (rpcServerWithQueueSize != nullptr)
			{
				rpcServerWithQueueSize->ProcessPendingRequests();
			}
		}
		catch (Platform::COMException^ ex)
		{
			return ex->HResult;
		}

		return S_OK;
	}

	RpcHookCode TestHooks::NoInputPlayerOutput(RpcArchive^ input, RpcArchive^ output)
	{
		UNREFERENCED_PARAMETER(input);
		UNREFERENCED_PARAMETER(output);
		
		try
		{
			Player^ p = Player::InitializeOutgoingPlayer();

			p->ToRpcArchive(output);
		}
		catch(Platform::COMException^ ex)
		{
			return ex->HResult;
		}
		return S_OK;
	}

	RpcHookCode TestHooks::InputPlayerOutputPlayer(RpcArchive^ input, RpcArchive^ output)
	{		
		try
		{
			InputPlayerNoOutput(input, output);
			NoInputPlayerOutput(input, output);
	
		}
		catch(Platform::COMException^ ex)
		{
			return ex->HResult;
		}

		return S_OK;
	}

	RpcHookCode TestHooks::VerifyIncomingPlayerData(Player^ playerData)
	{
		if (!(abs(playerData->health - 76.375) < 0.0001))
		{
            return E_INVALIDARG;
        }

        if (playerData->name != "Brian The Awesome")
		{
            return E_INVALIDARG;
        }

        if (playerData->spells->Length != ExpectedNumSpells)
		{
            return E_INVALIDARG;
        }

        if (playerData->spells[0] != "Heal")
		{
            return E_INVALIDARG;
        }

        if (playerData->spells[1] != "Rage")
		{
            return E_INVALIDARG;
        }

        if (playerData->spells[2] != "Morph")
		{
            return E_INVALIDARG;
        }

        if (playerData->spells[3] != "Haste")
		{
            return E_INVALIDARG;
        }

        if (playerData->weapons->Length != ExpectedNumWeapons)
		{
            return E_INVALIDARG;
        }

        if (playerData->weapons[0]->damage != 1411518807)
		{
            return E_INVALIDARG;
        }

        if (!(abs(playerData->weapons[0]->modifier - 64848978978.8971) < 0.0001))
		{
            return E_INVALIDARG;
        }

        if (playerData->weapons[1]->damage != 576460752)
		{
            return E_INVALIDARG;
        }

        if (!(abs(playerData->weapons[1]->modifier - -1796456897.68642) < 0.0001))
		{
            return E_INVALIDARG;
        }

        if (playerData->stature->height != 73) 
		{
            return E_INVALIDARG;
        }

        if (playerData->stature->weight != 189)
		{
            return E_INVALIDARG;
        }

		return S_OK;
	}
};